<!DOCTYPE HTML>
<html lang="zh-CN">


<head>
    <meta charset="utf-8">
    <meta name="keywords" content="Go并发编程整理, 博客">
    <meta name="description" content="个人学习储备知识与经验的秘密基地">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <meta name="renderer" content="webkit|ie-stand|ie-comp">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="format-detection" content="telephone=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <!-- Global site tag (gtag.js) - Google Analytics -->

<script async src="https://www.googletagmanager.com/gtag/js?id=G-KDXT8Y4CLW"></script>
<script>
    window.dataLayer = window.dataLayer || [];
    function gtag() {
        dataLayer.push(arguments);
    }

    gtag('js', new Date());
    gtag('config', 'G-KDXT8Y4CLW');
</script>


    <title>Go并发编程整理 | 不二博客</title>
    <link rel="icon" type="image/png" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/favicon.png">

    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/awesome/css/all.css">
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/materialize/materialize.min.css">
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/aos/aos.css">
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/animate/animate.min.css">
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/lightGallery/css/lightgallery.min.css">
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/css/matery.css">
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/css/my.css">

    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/jquery/jquery.min.js"></script>

<meta name="generator" content="Hexo 6.3.0">
<style>.github-emoji { position: relative; display: inline-block; width: 1.2em; min-height: 1.2em; overflow: hidden; vertical-align: top; color: transparent; }  .github-emoji > span { position: relative; z-index: 10; }  .github-emoji img, .github-emoji .fancybox { margin: 0 !important; padding: 0 !important; border: none !important; outline: none !important; text-decoration: none !important; user-select: none !important; cursor: auto !important; }  .github-emoji img { height: 1.2em !important; width: 1.2em !important; position: absolute !important; left: 50% !important; top: 50% !important; transform: translate(-50%, -50%) !important; user-select: none !important; cursor: auto !important; } .github-emoji-fallback { color: inherit; } .github-emoji-fallback img { opacity: 0 !important; }</style>
<link rel="alternate" href="/atom.xml" title="不二博客" type="application/atom+xml">
</head>




<body>
    <header class="navbar-fixed">
    <nav id="headNav" class="bg-color nav-transparent">
        <div id="navContainer" class="nav-wrapper container">
            <div class="brand-logo">
                <a href="/" class="waves-effect waves-light">
                    
                    <img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/medias/logo.png" class="logo-img" alt="LOGO">
                    
                    <span class="logo-span">不二博客</span>
                </a>
            </div>
            

<a href="#" data-target="mobile-nav" class="sidenav-trigger button-collapse"><i class="fas fa-bars"></i></a>
<ul class="right nav-menu">
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/" class="waves-effect waves-light">
      
      <i class="fas fa-home" style="zoom: 0.6;"></i>
      
      <span>首页</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/tags" class="waves-effect waves-light">
      
      <i class="fas fa-tags" style="zoom: 0.6;"></i>
      
      <span>标签</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/categories" class="waves-effect waves-light">
      
      <i class="fas fa-bookmark" style="zoom: 0.6;"></i>
      
      <span>分类</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/archives" class="waves-effect waves-light">
      
      <i class="fas fa-archive" style="zoom: 0.6;"></i>
      
      <span>归档</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/about" class="waves-effect waves-light">
      
      <i class="fas fa-user-circle" style="zoom: 0.6;"></i>
      
      <span>关于</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/contact" class="waves-effect waves-light">
      
      <i class="fas fa-comments" style="zoom: 0.6;"></i>
      
      <span>留言板</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/friends" class="waves-effect waves-light">
      
      <i class="fas fa-address-book" style="zoom: 0.6;"></i>
      
      <span>友情链接</span>
    </a>
    
  </li>
  
  <li>
    <a href="#searchModal" class="modal-trigger waves-effect waves-light">
      <i id="searchIcon" class="fas fa-search" title="搜索" style="zoom: 0.85;"></i>
    </a>
  </li>
</ul>


<div id="mobile-nav" class="side-nav sidenav">

    <div class="mobile-head bg-color">
        
        <img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/medias/logo.png" class="logo-img circle responsive-img">
        
        <div class="logo-name">不二博客</div>
        <div class="logo-desc">
            
            个人学习储备知识与经验的秘密基地
            
        </div>
    </div>

    

    <ul class="menu-list mobile-menu-list">
        
        <li class="m-nav-item">
	  
		<a href="/" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-home"></i>
			
			首页
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/tags" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-tags"></i>
			
			标签
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/categories" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-bookmark"></i>
			
			分类
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/archives" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-archive"></i>
			
			归档
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/about" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-user-circle"></i>
			
			关于
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/contact" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-comments"></i>
			
			留言板
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/friends" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-address-book"></i>
			
			友情链接
		</a>
          
        </li>
        
        
        <li><div class="divider"></div></li>
        <li>
            <a href="https://github.com/weiyouwozuiku" class="waves-effect waves-light" target="_blank">
                <i class="fab fa-github-square fa-fw"></i>Fork Me
            </a>
        </li>
        
    </ul>
</div>


        </div>

        
            <style>
    .nav-transparent .github-corner {
        display: none !important;
    }

    .github-corner {
        position: absolute;
        z-index: 10;
        top: 0;
        right: 0;
        border: 0;
        transform: scale(1.1);
    }

    .github-corner svg {
        color: #0f9d58;
        fill: #fff;
        height: 64px;
        width: 64px;
    }

    .github-corner:hover .octo-arm {
        animation: a 0.56s ease-in-out;
    }

    .github-corner .octo-arm {
        animation: none;
    }

    @keyframes a {
        0%,
        to {
            transform: rotate(0);
        }
        20%,
        60% {
            transform: rotate(-25deg);
        }
        40%,
        80% {
            transform: rotate(10deg);
        }
    }
</style>

<a href="https://github.com/weiyouwozuiku" class="github-corner tooltipped hide-on-med-and-down" target="_blank"
   data-tooltip="Fork Me" data-position="left" data-delay="50">
    <svg viewBox="0 0 250 250" aria-hidden="true">
        <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
        <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
              fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
        <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
              fill="currentColor" class="octo-body"></path>
    </svg>
</a>
        
    </nav>

</header>

    
<script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/cryptojs/crypto-js.min.js"></script>
<script>
    (function() {
        let pwd = '';
        if (pwd && pwd.length > 0) {
            if (pwd !== CryptoJS.SHA256(prompt('请输入访问本文章的密码')).toString(CryptoJS.enc.Hex)) {
                alert('密码错误，将返回主页！');
                location.href = '/';
            }
        }
    })();
</script>




<div class="bg-cover pd-header post-cover" style="background-image: url('https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/PageImg/程序设计/Go并发编程整理.jpg')">
    <div class="container" style="right: 0px;left: 0px;">
        <div class="row">
            <div class="col s12 m12 l12">
                <div class="brand">
                    <h1 class="description center-align post-title">Go并发编程整理</h1>
                </div>
            </div>
        </div>
    </div>
</div>




<main class="post-container content">

    
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/tocbot/tocbot.css">
<style>
    #articleContent h1::before,
    #articleContent h2::before,
    #articleContent h3::before,
    #articleContent h4::before,
    #articleContent h5::before,
    #articleContent h6::before {
        display: block;
        content: " ";
        height: 100px;
        margin-top: -100px;
        visibility: hidden;
    }

    #articleContent :focus {
        outline: none;
    }

    .toc-fixed {
        position: fixed;
        top: 64px;
    }

    .toc-widget {
        width: 345px;
        padding-left: 20px;
    }

    .toc-widget .toc-title {
        padding: 35px 0 15px 17px;
        font-size: 1.5rem;
        font-weight: bold;
        line-height: 1.5rem;
    }

    .toc-widget ol {
        padding: 0;
        list-style: none;
    }

    #toc-content {
        padding-bottom: 30px;
        overflow: auto;
    }

    #toc-content ol {
        padding-left: 10px;
    }

    #toc-content ol li {
        padding-left: 10px;
    }

    #toc-content .toc-link:hover {
        color: #42b983;
        font-weight: 700;
        text-decoration: underline;
    }

    #toc-content .toc-link::before {
        background-color: transparent;
        max-height: 25px;

        position: absolute;
        right: 23.5vw;
        display: block;
    }

    #toc-content .is-active-link {
        color: #42b983;
    }

    #floating-toc-btn {
        position: fixed;
        right: 15px;
        bottom: 76px;
        padding-top: 15px;
        margin-bottom: 0;
        z-index: 998;
    }

    #floating-toc-btn .btn-floating {
        width: 48px;
        height: 48px;
    }

    #floating-toc-btn .btn-floating i {
        line-height: 48px;
        font-size: 1.4rem;
    }
</style>
<div class="row">
    <div id="main-content" class="col s12 m12 l9">
        <!-- 文章内容详情 -->
<div id="artDetail">
    <div class="card">
        <div class="card-content article-info">
            <div class="row tag-cate">
                <div class="col s7">
                    
                    <div class="article-tag">
                        
                            <a href="/tags/Go/">
                                <span class="chip bg-color">Go</span>
                            </a>
                        
                    </div>
                    
                </div>
                <div class="col s5 right-align">
                    
                    <div class="post-cate">
                        <i class="fas fa-bookmark fa-fw icon-category"></i>
                        
                            <a href="/categories/%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/" class="post-category">
                                程序设计
                            </a>
                        
                    </div>
                    
                </div>
            </div>

            <div class="post-info">
                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-minus fa-fw"></i>发布日期:&nbsp;&nbsp;
                    2022-03-12
                </div>
                

                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-check fa-fw"></i>更新日期:&nbsp;&nbsp;
                    2022-10-31
                </div>
                

                
                <div class="info-break-policy">
                    <i class="far fa-file-word fa-fw"></i>文章字数:&nbsp;&nbsp;
                    8k
                </div>
                

                
                <div class="info-break-policy">
                    <i class="far fa-clock fa-fw"></i>阅读时长:&nbsp;&nbsp;
                    30 分
                </div>
                

                
                    <div id="busuanzi_container_page_pv" class="info-break-policy">
                        <i class="far fa-eye fa-fw"></i>阅读次数:&nbsp;&nbsp;
                        <span id="busuanzi_value_page_pv"></span>
                    </div>
				
            </div>
        </div>
        <hr class="clearfix">

        
        <!-- 是否加载使用自带的 prismjs. -->
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/prism/prism.css">
        

        
        <!-- 代码块折行 -->
        <style type="text/css">
            code[class*="language-"], pre[class*="language-"] { white-space: pre-wrap !important; }
        </style>
        

        <div class="card-content article-card-content">
            <div id="articleContent">
                <h2 id="Context">Context<a class="anchor" href="#Context">¶</a></h2>
<p>在Go1.7版本引入官方库，可以在API边界之间以及过程之间传递截止时间、取消时间或其他与请求相关的数据。</p>
<pre class="mermaid">graph TD;
Context--&gt;emptyCtx
Context--&gt;cancelCtx
Context--&gt;timerCtx
Context--&gt;valueCtx
Context--&gt;Backgroup
Context--&gt;TODO
Context--&gt;WithCancel
Context--&gt;WithDeadline
Context--&gt;WithTimeout
Context--&gt;WithValue</pre>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">type</span> Context <span class="token keyword">interface</span> <span class="token punctuation">{</span>
	<span class="token function">Deadline</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>deadline time<span class="token punctuation">.</span>Time<span class="token punctuation">,</span> ok <span class="token builtin">bool</span><span class="token punctuation">)</span>
	<span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;-</span><span class="token keyword">chan</span> <span class="token keyword">struct</span><span class="token punctuation">{</span><span class="token punctuation">}</span>
	<span class="token function">Err</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">error</span>
	<span class="token function">Value</span><span class="token punctuation">(</span>key <span class="token keyword">interface</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">interface</span><span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<h2 id="基本并发原语">基本并发原语<a class="anchor" href="#基本并发原语">¶</a></h2>
<h3 id="互斥锁实现机制">互斥锁实现机制<a class="anchor" href="#互斥锁实现机制">¶</a></h3>
<p>共享资源在程序中的位置被称之为<strong>临界区</strong>，使用互斥锁可以限定临界区只能同时由一个线程所有。当临界区由一个线程持有的时候，其它线程如果想进入这个临界区，就会返回失败，或者是等待。直到持有的线程退出临界区，这些等待线程中的某一个才有机会接着持有这个临界区。</p>
<h3 id="同步原语">同步原语<a class="anchor" href="#同步原语">¶</a></h3>
<p>同步原语就是解决并发问题的一个基础数据结构。</p>
<p>适用场景：</p>
<ul>
<li>共享资源：并发地读写共享资源，会出现数据竞争（data race）的问题，所以需要 Mutex、RWMutex 这样的并发原语来保护。</li>
<li>任务编排：需要 goroutine 按照一定的规律执行，而 goroutine 之间有相互等待或者依赖的顺序关系，我们常常使用 WaitGroup 或者 Channel 来实现。</li>
<li>消息传递：信息交流以及不同的 goroutine 之间的线程安全的数据交流，常常使用 Channel 来实现。</li>
</ul>
<h3 id="Locker接口">Locker接口<a class="anchor" href="#Locker接口">¶</a></h3>
<p>在 Go 的标准库中，package sync 提供了锁相关的一系列同步原语，这个 package 还定义了一个 Locker 的接口，Mutex 就实现了这个接口。</p>
<p>Locker 的接口定义了锁同步原语的方法集：</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">type</span> Locker <span class="token keyword">interface</span> <span class="token punctuation">{</span>
    <span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
<p>但是实际工程中一般直接使用具体的同步原语，而不是接口。</p>
<h3 id="Mutex">Mutex<a class="anchor" href="#Mutex">¶</a></h3>
<p>Mutex是使用最广泛的同步原语。</p>
<p><strong>互斥锁 Mutex 就提供两个方法 Lock 和 Unlock：进入临界区之前调用 Lock 方法，退出临界区的时候调用 Unlock 方法</strong>。</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span><span class="token punctuation">(</span>m <span class="token operator">*</span>Mutex<span class="token punctuation">)</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">func</span><span class="token punctuation">(</span>m <span class="token operator">*</span>Mutex<span class="token punctuation">)</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>
<p><strong>当一个 goroutine 通过调用 Lock 方法获得了这个锁的拥有权后， 其它请求锁的 goroutine 就会阻塞在 Lock 方法的调用上，直到锁被释放并且自己获取到了这个锁的拥有权。</strong></p>
<h4 id="使用">使用<a class="anchor" href="#使用">¶</a></h4>
<h5 id="直接使用">直接使用<a class="anchor" href="#直接使用">¶</a></h5>
<pre class="line-numbers language-go" data-language="go"><code class="language-go">mu<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
count<span class="token operator">++</span>
mu<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>
<p>Mutex 的零值是还没有 goroutine 等待的未加锁的状态，所以你不需要额外的初始化，直接声明变量（如 var mu sync.Mutex）即可。</p>
<h5 id="嵌入struct">嵌入struct<a class="anchor" href="#嵌入struct">¶</a></h5>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">type</span> Counter <span class="token keyword">struct</span> <span class="token punctuation">{</span>
    mu    sync<span class="token punctuation">.</span>Mutex
    Count <span class="token builtin">uint64</span>
<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
<p>在初始化嵌入的 struct 时，也不必初始化这个 Mutex 字段，不会因为没有初始化出现空指针或者是无法获取到锁的情况。</p>
<h5 id="嵌入字段">嵌入字段<a class="anchor" href="#嵌入字段">¶</a></h5>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">var</span> counter Counter
    <span class="token keyword">var</span> wg sync<span class="token punctuation">.</span>WaitGroup
    wg<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span>
    <span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
        <span class="token keyword">go</span> <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">defer</span> wg<span class="token punctuation">.</span><span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token keyword">for</span> j <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> <span class="token number">100000</span><span class="token punctuation">;</span> j<span class="token operator">++</span> <span class="token punctuation">{</span>
                counter<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
                counter<span class="token punctuation">.</span>Count<span class="token operator">++</span>
                counter<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
    wg<span class="token punctuation">.</span><span class="token function">Wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>counter<span class="token punctuation">.</span>Count<span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">type</span> Counter <span class="token keyword">struct</span> <span class="token punctuation">{</span>
    sync<span class="token punctuation">.</span>Mutex
    Count <span class="token builtin">uint64</span>
<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>**如果嵌入的 struct 有多个字段，我们一般会把 Mutex 放在要控制的字段上面，然后使用空格把字段分隔开来。**即使你不这样做，代码也可以正常编译，只不过，用这种风格去写的话，逻辑会更清晰，也更易于维护。</p>
<p>甚至，你还可以<strong>把获取锁、释放锁、计数加一的逻辑封装成一个方法</strong>，对外不需要暴露锁等逻辑。</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 封装好的计数器</span>
    <span class="token keyword">var</span> counter Counter

    <span class="token keyword">var</span> wg sync<span class="token punctuation">.</span>WaitGroup
    wg<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span>

    <span class="token comment">// 启动10个goroutine</span>
    <span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
        <span class="token keyword">go</span> <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">defer</span> wg<span class="token punctuation">.</span><span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token comment">// 执行10万次累加</span>
            <span class="token keyword">for</span> j <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> <span class="token number">100000</span><span class="token punctuation">;</span> j<span class="token operator">++</span> <span class="token punctuation">{</span>
                counter<span class="token punctuation">.</span><span class="token function">Incr</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 受到锁保护的方法</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
    wg<span class="token punctuation">.</span><span class="token function">Wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>counter<span class="token punctuation">.</span><span class="token function">Count</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token comment">// 线程安全的计数器类型</span>
<span class="token keyword">type</span> Counter <span class="token keyword">struct</span> <span class="token punctuation">{</span>
    CounterType <span class="token builtin">int</span>
    Name        <span class="token builtin">string</span>

    mu    sync<span class="token punctuation">.</span>Mutex
    count <span class="token builtin">uint64</span>
<span class="token punctuation">}</span>

<span class="token comment">// 加1的方法，内部使用互斥锁保护</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>c <span class="token operator">*</span>Counter<span class="token punctuation">)</span> <span class="token function">Incr</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    c<span class="token punctuation">.</span>mu<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    c<span class="token punctuation">.</span>count<span class="token operator">++</span>
    c<span class="token punctuation">.</span>mu<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token comment">// 得到计数器的值，也需要锁保护</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>c <span class="token operator">*</span>Counter<span class="token punctuation">)</span> <span class="token function">Count</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">uint64</span> <span class="token punctuation">{</span>
    c<span class="token punctuation">.</span>mu<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">defer</span> c<span class="token punctuation">.</span>mu<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">return</span> c<span class="token punctuation">.</span>count
<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<h4 id="实现">实现<a class="anchor" href="#实现">¶</a></h4>
<p>Mutex的实现从原本简单的设计逐渐变得精密，以下分阶段描述Mutex实现。</p>
<p><img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/Go/Go%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E6%95%B4%E7%90%86/Mutex%E5%AE%9E%E7%8E%B0%E5%9B%9B%E9%98%B6%E6%AE%B5.jpg" alt="Mutex实现四阶段.jpg"></p>
<h5 id="初版">初版<a class="anchor" href="#初版">¶</a></h5>
<p>uss Cox 在 2008 年提交的第一版 Mutex中的实现方式：可以通过一个 flag 变量，标记当前的锁是否被某个 goroutine 持有。如果这个 flag 的值是 1，就代表锁已经被持有，那么，其它竞争的 goroutine 只能等待；如果这个 flag 的值是 0，就可以通过 CAS（compare-and-swap，或者 compare-and-set）将这个 flag 设置为 1，标识锁被当前的这个 goroutine 持有了。</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token comment">// CAS操作，当时还没有抽象出atomic包</span>
 <span class="token keyword">func</span> <span class="token function">cas</span><span class="token punctuation">(</span>val <span class="token operator">*</span><span class="token builtin">int32</span><span class="token punctuation">,</span> old<span class="token punctuation">,</span> <span class="token builtin">new</span> <span class="token builtin">int32</span><span class="token punctuation">)</span> <span class="token builtin">bool</span>
 <span class="token keyword">func</span> <span class="token function">semacquire</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token builtin">int32</span><span class="token punctuation">)</span>
 <span class="token keyword">func</span> <span class="token function">semrelease</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token builtin">int32</span><span class="token punctuation">)</span>
 <span class="token comment">// 互斥锁的结构，包含两个字段</span>
 <span class="token keyword">type</span> Mutex <span class="token keyword">struct</span> <span class="token punctuation">{</span>
     key  <span class="token builtin">int32</span> <span class="token comment">// 锁是否被持有的标识</span>
     sema <span class="token builtin">int32</span> <span class="token comment">// 信号量专用，用以阻塞/唤醒goroutine</span>
 <span class="token punctuation">}</span>
 
 <span class="token comment">// 保证成功在val上增加delta的值</span>
 <span class="token keyword">func</span> <span class="token function">xadd</span><span class="token punctuation">(</span>val <span class="token operator">*</span><span class="token builtin">int32</span><span class="token punctuation">,</span> delta <span class="token builtin">int32</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token builtin">new</span> <span class="token builtin">int32</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
     <span class="token keyword">for</span> <span class="token punctuation">{</span>
         v <span class="token operator">:=</span> <span class="token operator">*</span>val
         <span class="token keyword">if</span> <span class="token function">cas</span><span class="token punctuation">(</span>val<span class="token punctuation">,</span> v<span class="token punctuation">,</span> v<span class="token operator">+</span>delta<span class="token punctuation">)</span> <span class="token punctuation">{</span>
             <span class="token keyword">return</span> v <span class="token operator">+</span> delta
         <span class="token punctuation">}</span>
     <span class="token punctuation">}</span>
     <span class="token function">panic</span><span class="token punctuation">(</span><span class="token string">"unreached"</span><span class="token punctuation">)</span>
 <span class="token punctuation">}</span>
 
 <span class="token comment">// 请求锁</span>
 <span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>Mutex<span class="token punctuation">)</span> <span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
     <span class="token keyword">if</span> <span class="token function">xadd</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>key<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">1</span> <span class="token punctuation">{</span> <span class="token comment">//标识加1，如果等于1，成功获取到锁</span>
         <span class="token keyword">return</span>
     <span class="token punctuation">}</span>
     <span class="token function">semacquire</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>sema<span class="token punctuation">)</span> <span class="token comment">// 否则阻塞等待</span>
 <span class="token punctuation">}</span>
 
 <span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>Mutex<span class="token punctuation">)</span> <span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
     <span class="token keyword">if</span> <span class="token function">xadd</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>key<span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token comment">// 将标识减去1，如果等于0，则没有其它等待者</span>
         <span class="token keyword">return</span>
     <span class="token punctuation">}</span>
     <span class="token function">semrelease</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>sema<span class="token punctuation">)</span> <span class="token comment">// 唤醒其它阻塞的goroutine</span>
 <span class="token punctuation">}</span>    <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>CAS需要有3个操作数：内存地址V，旧的预期值A，即将要更新的目标值B。CAS 指令将<strong>给定的值</strong>和<strong>一个内存地址中的值</strong>进行比较，如果它们是同一个值，就使用新值替换内存地址中的值，这个操作是原子性的。</p>
<p><strong>原子性保证这个指令总是基于最新的值进行计算，如果同时有其它线程已经修改了这个值，那么，CAS 会返回失败</strong>。</p>
<p><img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/Go/Go%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E6%95%B4%E7%90%86/%E5%88%9D%E7%89%88Mutex.jpg" alt="初版Mutex.jpg"></p>
<p>调用 Lock 请求锁的时候，通过 xadd 方法进行 CAS 操作（第 24 行），xadd 方法通过循环执行 CAS 操作直到成功，保证对 key 加 1 的操作成功完成。如果比较幸运，锁没有被别的 goroutine 持有，那么，Lock 方法成功地将 key 设置为 1，这个 goroutine 就持有了这个锁；如果锁已经被别的 goroutine 持有了，那么，当前的 goroutine 会把 key 加 1，而且还会调用 semacquire 方法（第 27 行），使用信号量将自己休眠，等锁释放的时候，信号量会将它唤醒。</p>
<p>持有锁的 goroutine 调用 Unlock 释放锁时，它会将 key 减 1（第 31 行）。如果当前没有其它等待这个锁的 goroutine，这个方法就返回了。但是，如果还有等待此锁的其它 goroutine，那么，它会调用 semrelease 方法（第 34 行），利用信号量唤醒等待锁的其它 goroutine 中的一个。</p>
<p>初版的 Mutex 利用 CAS 原子操作，对 key 这个标志量进行设置。key 不仅仅标识了锁是否被 goroutine 所持有，还记录了当前持有和等待获取锁的 goroutine 的数量。</p>
<p><strong>Unlock 方法可以被任意的 goroutine 调用释放锁，即使是没持有这个互斥锁的 goroutine，也可以进行这个操作。这是因为，Mutex 本身并没有包含持有这把锁的 goroutine 的信息，所以，Unlock 也不会对此进行检查。Mutex 的这个设计一直保持至今。</strong></p>
<p>所以，<strong>在使用 Mutex 的时候，必须要保证 goroutine 尽可能不去释放自己未持有的锁</strong>，一定要遵循“<strong>谁申请，谁释放</strong>”的原则。在真实的实践中，我们使用互斥锁的时候，很少在一个方法中单独申请锁，而在另外一个方法中单独释放锁，一般都会在同一个方法中获取锁和释放锁。</p>
<p>推荐写法</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token punctuation">(</span>f <span class="token operator">*</span>Foo<span class="token punctuation">)</span> <span class="token function">Bar</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    f<span class="token punctuation">.</span>mu<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">defer</span> f<span class="token punctuation">.</span>mu<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token keyword">if</span> f<span class="token punctuation">.</span>count <span class="token operator">&lt;</span> <span class="token number">1000</span> <span class="token punctuation">{</span>
        f<span class="token punctuation">.</span>count <span class="token operator">+=</span> <span class="token number">3</span>
        <span class="token keyword">return</span>
    <span class="token punctuation">}</span>

    f<span class="token punctuation">.</span>count<span class="token operator">++</span>
    <span class="token keyword">return</span>
<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>如果临界区只是方法中的一部分，为了尽快释放锁，还是应该第一时间调用 Unlock，而不是一直等到方法返回时才释放。</p>
<p>但是，初版的 Mutex 实现有一个问题：请求锁的 goroutine 会排队等待获取互斥锁。虽然这貌似很公平，但是从性能上来看，却不是最优的。因为如果我们能够把锁交给正在占用 CPU 时间片的 goroutine 的话，那就不需要做上下文的切换，在高并发的情况下，可能会有更好的性能。因此进入下一个阶段：给新人机会。</p>
<h5 id="给新人机会">给新人机会<a class="anchor" href="#给新人机会">¶</a></h5>
<p>Go 开发者在 2011 年 6 月 30 日的 commit 中对 Mutex 做了一次大的调整，调整后的 Mutex 实现如下：</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">type</span> Mutex <span class="token keyword">struct</span> <span class="token punctuation">{</span>
     state <span class="token builtin">int32</span>
     sema  <span class="token builtin">uint32</span>
 <span class="token punctuation">}</span>

 <span class="token keyword">const</span> <span class="token punctuation">(</span>
     mutexLocked <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">&lt;&lt;</span> <span class="token boolean">iota</span> <span class="token comment">// mutex is locked</span>
     mutexWoken
     mutexWaiterShift <span class="token operator">=</span> <span class="token boolean">iota</span>
 <span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>其中state字段是一个复合字段，一个字段包含多个意义，这样可以通过尽可能少的内存来实现互斥锁。这个字段的第一位（最小的一位）来表示这个锁是否被持有，第二位代表是否有唤醒的 goroutine，剩余的位数代表的是等待此锁的 goroutine 数。所以，state 这一个字段被分成了三部分，代表三个数据。</p>
<p><img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/Go/Go%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E6%95%B4%E7%90%86/%E7%BB%99%E6%96%B0%E4%BA%BA%E6%9C%BA%E4%BC%9A_state%E5%AD%97%E6%AE%B5.jpg" alt="给新人机会_state字段.jpg"></p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>Mutex<span class="token punctuation">)</span> <span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
     <span class="token comment">// Fast path: 幸运case，能够直接获取到锁</span>
     <span class="token keyword">if</span> atomic<span class="token punctuation">.</span><span class="token function">CompareAndSwapInt32</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>state<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> mutexLocked<span class="token punctuation">)</span> <span class="token punctuation">{</span>
         <span class="token keyword">return</span>
     <span class="token punctuation">}</span>

     awoke <span class="token operator">:=</span> <span class="token boolean">false</span>
     <span class="token keyword">for</span> <span class="token punctuation">{</span>
         old <span class="token operator">:=</span> m<span class="token punctuation">.</span>state
         <span class="token builtin">new</span> <span class="token operator">:=</span> old <span class="token operator">|</span> mutexLocked <span class="token comment">// 新状态加锁</span>
         <span class="token keyword">if</span> old<span class="token operator">&amp;</span>mutexLocked <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span>
             <span class="token builtin">new</span> <span class="token operator">=</span> old <span class="token operator">+</span> <span class="token number">1</span><span class="token operator">&lt;&lt;</span>mutexWaiterShift <span class="token comment">//等待者数量加一</span>
         <span class="token punctuation">}</span>
         <span class="token keyword">if</span> awoke <span class="token punctuation">{</span>
             <span class="token comment">// goroutine是被唤醒的，</span>
             <span class="token comment">// 新状态清除唤醒标志</span>
             <span class="token builtin">new</span> <span class="token operator">&amp;^=</span> mutexWoken
         <span class="token punctuation">}</span>
         <span class="token keyword">if</span> atomic<span class="token punctuation">.</span><span class="token function">CompareAndSwapInt32</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>state<span class="token punctuation">,</span> old<span class="token punctuation">,</span> <span class="token builtin">new</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token comment">//设置新状态</span>
             <span class="token keyword">if</span> old<span class="token operator">&amp;</span>mutexLocked <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token comment">// 锁原状态未加锁</span>
                 <span class="token keyword">break</span>
             <span class="token punctuation">}</span>
             runtime<span class="token punctuation">.</span><span class="token function">Semacquire</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>sema<span class="token punctuation">)</span> <span class="token comment">// 请求信号量</span>
             awoke <span class="token operator">=</span> <span class="token boolean">true</span>
         <span class="token punctuation">}</span>
     <span class="token punctuation">}</span>
 <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>首先是通过 CAS 检测 state 字段中的标志（第 3 行），如果没有 goroutine 持有锁，也没有等待持有锁的 gorutine，那么，当前的 goroutine 就很幸运，可以直接获得锁，这也是注释中的 Fast path 的意思。</p>
<p>如果不够幸运，state 不是零值，那么就通过一个循环进行检查。接下来的第 7 行到第 26 行这段代码虽然只有几行，但是理解起来却要费一番功夫，因为涉及到对 state 不同标志位的操作。这里的位操作以及操作后的结果和数值比较，并没有明确的解释，有时候你需要根据后续的处理进行推断。所以说，如果你充分理解了这段代码，那么对最新版的 Mutex 也会比较容易掌握了，因为你已经清楚了这些位操作的含义。</p>
<p>我们先前知道，如果想要获取锁的 goroutine 没有机会获取到锁，就会进行休眠，但是在锁释放唤醒之后，它并不能像先前一样直接获取到锁，还是要和正在请求锁的 goroutine 进行竞争。这会给后来请求锁的 goroutine 一个机会，也让 CPU 中正在执行的 goroutine 有更多的机会获取到锁，在一定程度上提高了程序的性能。</p>
<p>for 循环是不断尝试获取锁，如果获取不到，就通过 runtime.Semacquire(&amp;m.sema) 休眠，休眠醒来之后 awoke 置为 true，尝试争抢锁。</p>
<p>代码中的第 10 行将当前的 flag 设置为加锁状态，如果能成功地通过 CAS 把这个新值赋予 state（第 19 行和第 20 行），就代表抢夺锁的操作成功了。</p>
<p>不过，需要注意的是，如果成功地设置了 state 的值，但是之前的 state 是有锁的状态，那么，state 只是清除 mutexWoken 标志或者增加一个 waiter 而已。</p>
<p>请求锁的 goroutine 有两类，一类是新来请求锁的 goroutine，另一类是被唤醒的等待请求锁的 goroutine。锁的状态也有两种：加锁和未加锁。我用一张表格，来说明一下 goroutine 不同来源不同状态下的处理逻辑。</p>
<p><img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/Go/Go%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E6%95%B4%E7%90%86/%E7%BB%99%E6%96%B0%E4%BA%BA%E6%9C%BA%E4%BC%9A_%E4%B8%A4%E7%B1%BBgoroutine.jpg" alt="给新人机会_两类goroutine.jpg"></p>
<p>上述是Lock操作，以下是Unlock操作：</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>Mutex<span class="token punctuation">)</span> <span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
     <span class="token comment">// Fast path: drop lock bit.</span>
     <span class="token builtin">new</span> <span class="token operator">:=</span> atomic<span class="token punctuation">.</span><span class="token function">AddInt32</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>state<span class="token punctuation">,</span> <span class="token operator">-</span>mutexLocked<span class="token punctuation">)</span> <span class="token comment">//去掉锁标志</span>
     <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token builtin">new</span><span class="token operator">+</span>mutexLocked<span class="token punctuation">)</span><span class="token operator">&amp;</span>mutexLocked <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token comment">//本来就没有加锁</span>
         <span class="token function">panic</span><span class="token punctuation">(</span><span class="token string">"sync: unlock of unlocked mutex"</span><span class="token punctuation">)</span>
     <span class="token punctuation">}</span>
 
     old <span class="token operator">:=</span> <span class="token builtin">new</span>
     <span class="token keyword">for</span> <span class="token punctuation">{</span>
         <span class="token keyword">if</span> old<span class="token operator">&gt;&gt;</span>mutexWaiterShift <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">||</span> old<span class="token operator">&amp;</span><span class="token punctuation">(</span>mutexLocked<span class="token operator">|</span>mutexWoken<span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token comment">// 没有等待者，或者有唤醒的waiter，或者锁原来已加锁</span>
             <span class="token keyword">return</span>
         <span class="token punctuation">}</span>
         <span class="token builtin">new</span> <span class="token operator">=</span> <span class="token punctuation">(</span>old <span class="token operator">-</span> <span class="token number">1</span><span class="token operator">&lt;&lt;</span>mutexWaiterShift<span class="token punctuation">)</span> <span class="token operator">|</span> mutexWoken <span class="token comment">// 新状态，准备唤醒goroutine，并设置唤醒标志</span>
         <span class="token keyword">if</span> atomic<span class="token punctuation">.</span><span class="token function">CompareAndSwapInt32</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>state<span class="token punctuation">,</span> old<span class="token punctuation">,</span> <span class="token builtin">new</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
             runtime<span class="token punctuation">.</span><span class="token function">Semrelease</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>sema<span class="token punctuation">)</span>
             <span class="token keyword">return</span>
         <span class="token punctuation">}</span>
         old <span class="token operator">=</span> m<span class="token punctuation">.</span>state
     <span class="token punctuation">}</span>
 <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>第 3 行是尝试将持有锁的标识设置为未加锁的状态，这是通过减 1 而不是将标志位置零的方式实现。第 4 到 6 行还会检测原来锁的状态是否已经未加锁的状态，如果是 Unlock 一个未加锁的 Mutex 会直接 panic。</p>
<p>不过，即使将加锁置为未加锁的状态，这个方法也不能直接返回，还需要一些额外的操作，因为还可能有一些等待这个锁的 goroutine（有时候我也把它们称之为 waiter）需要通过信号量的方式唤醒它们中的一个。所以接下来的逻辑有两种情况。</p>
<p>第一种情况，如果没有其它的 waiter，说明对这个锁的竞争的 goroutine 只有一个，那就可以直接返回了；如果这个时候有唤醒的 goroutine，或者是又被别人加了锁，那么，无需我们操劳，其它 goroutine 自己干得都很好，当前的这个 goroutine 就可以放心返回了。</p>
<p>第二种情况，如果有等待者，并且没有唤醒的 waiter，那就需要唤醒一个等待的 waiter。在唤醒之前，需要将 waiter 数量减 1，并且将 mutexWoken 标志设置上，这样，Unlock 就可以返回了。</p>
<p><strong>这次的改动主要就是，新来的 goroutine 也有机会先获取到锁，甚至一个 goroutine 可能连续获取到锁，打破了先来先得的逻辑</strong></p>
<h5 id="多给些机会">多给些机会<a class="anchor" href="#多给些机会">¶</a></h5>
<p>首先介绍下什么是<code>自旋锁</code></p>
<p>自旋锁是指当一个线程在获取锁的时候，如果锁已经被其他线程获取，那么该线程将循环等待，然后不断地判断是否能够被成功获取，知直到获取到锁才会退出循环。</p>
<p>获取锁的线程一直处于活跃状态，但是并没有执行任何有效的任务，使用这种锁会造成<strong>busy-waiting</strong>。</p>
<p>它是为实现保护共享资源而提出的一种锁机制。其实，自旋锁与互斥锁比较类似，它们都是为了解决某项资源的互斥使用。无论是互斥锁，还是自旋锁，在任何时刻，最多只能由一个保持者，也就说，在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁，如果资源已经被占用，资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠，如果自旋锁已经被别的执行单元保持，调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁，“自旋”一词就是因此而得名[^3]。</p>
<p>在 2015 年 2 月的改动中，如果新来的 goroutine 或者是被唤醒的 goroutine 首次获取不到锁，它们就会通过自旋（spin，通过循环不断尝试，spin 的逻辑是在<a target="_blank" rel="noopener" href="https://github.com/golang/go/blob/846dce9d05f19a1f53465e62a304dea21b99f910/src/runtime/proc.go#L5580">runtime</a>实现的）的方式，尝试检查锁是否被释放。在尝试一定的自旋次数后，再执行原来的逻辑。</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>Mutex<span class="token punctuation">)</span> <span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
     <span class="token comment">// Fast path: 幸运之路，正好获取到锁</span>
     <span class="token keyword">if</span> atomic<span class="token punctuation">.</span><span class="token function">CompareAndSwapInt32</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>state<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> mutexLocked<span class="token punctuation">)</span> <span class="token punctuation">{</span>
         <span class="token keyword">return</span>
     <span class="token punctuation">}</span>

     awoke <span class="token operator">:=</span> <span class="token boolean">false</span>
     iter <span class="token operator">:=</span> <span class="token number">0</span>
     <span class="token keyword">for</span> <span class="token punctuation">{</span> <span class="token comment">// 不管是新来的请求锁的goroutine, 还是被唤醒的goroutine，都不断尝试请求锁</span>
         old <span class="token operator">:=</span> m<span class="token punctuation">.</span>state <span class="token comment">// 先保存当前锁的状态</span>
         <span class="token builtin">new</span> <span class="token operator">:=</span> old <span class="token operator">|</span> mutexLocked <span class="token comment">// 新状态设置加锁标志</span>
         <span class="token keyword">if</span> old<span class="token operator">&amp;</span>mutexLocked <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token comment">// 锁还没被释放</span>
             <span class="token keyword">if</span> <span class="token function">runtime_canSpin</span><span class="token punctuation">(</span>iter<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 还可以自旋</span>
                 <span class="token keyword">if</span> <span class="token operator">!</span>awoke <span class="token operator">&amp;&amp;</span> old<span class="token operator">&amp;</span>mutexWoken <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> old<span class="token operator">&gt;&gt;</span>mutexWaiterShift <span class="token operator">!=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span>
                     atomic<span class="token punctuation">.</span><span class="token function">CompareAndSwapInt32</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>state<span class="token punctuation">,</span> old<span class="token punctuation">,</span> old<span class="token operator">|</span>mutexWoken<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                     awoke <span class="token operator">=</span> <span class="token boolean">true</span>
                 <span class="token punctuation">}</span>
                 <span class="token function">runtime_doSpin</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
                 iter<span class="token operator">++</span>
                 <span class="token keyword">continue</span> <span class="token comment">// 自旋，再次尝试请求锁</span>
             <span class="token punctuation">}</span>
             <span class="token builtin">new</span> <span class="token operator">=</span> old <span class="token operator">+</span> <span class="token number">1</span><span class="token operator">&lt;&lt;</span>mutexWaiterShift
         <span class="token punctuation">}</span>
         <span class="token keyword">if</span> awoke <span class="token punctuation">{</span> <span class="token comment">// 唤醒状态</span>
             <span class="token keyword">if</span> <span class="token builtin">new</span><span class="token operator">&amp;</span>mutexWoken <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span>
                 <span class="token function">panic</span><span class="token punctuation">(</span><span class="token string">"sync: inconsistent mutex state"</span><span class="token punctuation">)</span>
             <span class="token punctuation">}</span>
             <span class="token builtin">new</span> <span class="token operator">&amp;^=</span> mutexWoken <span class="token comment">// 新状态清除唤醒标记</span>
         <span class="token punctuation">}</span>
         <span class="token keyword">if</span> atomic<span class="token punctuation">.</span><span class="token function">CompareAndSwapInt32</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>state<span class="token punctuation">,</span> old<span class="token punctuation">,</span> <span class="token builtin">new</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
             <span class="token keyword">if</span> old<span class="token operator">&amp;</span>mutexLocked <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token comment">// 旧状态锁已释放，新状态成功持有了锁，直接返回</span>
                 <span class="token keyword">break</span>
             <span class="token punctuation">}</span>
             <span class="token function">runtime_Semacquire</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>sema<span class="token punctuation">)</span> <span class="token comment">// 阻塞等待</span>
             awoke <span class="token operator">=</span> <span class="token boolean">true</span> <span class="token comment">// 被唤醒</span>
             iter <span class="token operator">=</span> <span class="token number">0</span>
         <span class="token punctuation">}</span>
     <span class="token punctuation">}</span>
 <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>这次的优化，增加了第 13 行到 21 行、第 25 行到第 27 行以及第 36 行。我来解释一下主要的逻辑，也就是第 13 行到 21 行。</p>
<p>如果可以 spin 的话，第 9 行的 for 循环会重新检查锁是否释放。对于临界区代码执行非常短的场景来说，这是一个非常好的优化。因为临界区的代码耗时很短，锁很快就能释放，而抢夺锁的 goroutine 不用通过休眠唤醒方式等待调度，直接 spin 几次，可能就获得了锁。</p>
<h5 id="解决饥饿">解决饥饿<a class="anchor" href="#解决饥饿">¶</a></h5>
<p>在之前的改进中，新来的goroutine也可以直接参与竞争，这可能导致一种极端情况的发生，就是等待中的goroutine一直获取不到锁。这就是饥饿问题。</p>
<p>Mutex 不能容忍这种事情发生。所以，2016 年 Go 1.9 中 Mutex 增加了饥饿模式，让锁变得更公平，不公平的等待时间限制在 1 毫秒，并且修复了一个大 Bug：总是把唤醒的 goroutine 放在等待队列的尾部，会导致更加不公平的等待时间。</p>
<p>之后，2018 年，Go 开发者将 fast path 和 slow path 拆成独立的方法，以便内联，提高性能。2019 年也有一个 Mutex 的优化，虽然没有对 Mutex 做修改，但是，对于 Mutex 唤醒后持有锁的那个 waiter，调度器可以有更高的优先级去执行，这已经是很细致的性能优化了。</p>
<p>现在的Mutex已经变得相当复杂了，<strong>Mutex 绝不容忍一个 goroutine 被落下，永远没有机会获取锁。不抛弃不放弃是它的宗旨，而且它也尽可能地让等待较长的 goroutine 更有机会获取到锁</strong>。</p>
<p>以下给出现在state字段的设计示意图：</p>
<p><img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/Go/Go%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E6%95%B4%E7%90%86/%E7%8E%B0%E9%98%B6%E6%AE%B5Mutex%E4%B8%ADstate%E5%AD%97%E6%AE%B5.jpg" alt="现阶段Mutex中state字段.jpg"></p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">type</span> Mutex <span class="token keyword">struct</span> <span class="token punctuation">{</span>
     state <span class="token builtin">int32</span>
     sema  <span class="token builtin">uint32</span>
 <span class="token punctuation">}</span>
 
 <span class="token keyword">const</span> <span class="token punctuation">(</span>
     mutexLocked <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">&lt;&lt;</span> <span class="token boolean">iota</span> <span class="token comment">// mutex is locked</span>
     mutexWoken
     mutexStarving <span class="token comment">// 从state字段中分出一个饥饿标记</span>
     mutexWaiterShift <span class="token operator">=</span> <span class="token boolean">iota</span>
 
     starvationThresholdNs <span class="token operator">=</span> <span class="token number">1e6</span> <span class="token comment">//饥饿模式的最大等待时间阈值设置成了 1 毫秒</span>
 <span class="token punctuation">)</span>
 
 <span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>Mutex<span class="token punctuation">)</span> <span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
     <span class="token comment">// Fast path: 幸运之路，一下就获取到了锁</span>
     <span class="token keyword">if</span> atomic<span class="token punctuation">.</span><span class="token function">CompareAndSwapInt32</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>state<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> mutexLocked<span class="token punctuation">)</span> <span class="token punctuation">{</span>
         <span class="token keyword">return</span>
     <span class="token punctuation">}</span>
     <span class="token comment">// Slow path：缓慢之路，尝试自旋竞争或饥饿状态下饥饿goroutine竞争</span>
     m<span class="token punctuation">.</span><span class="token function">lockSlow</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
 <span class="token punctuation">}</span>
 
 <span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>Mutex<span class="token punctuation">)</span> <span class="token function">lockSlow</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
     <span class="token keyword">var</span> waitStartTime <span class="token builtin">int64</span>
     starving <span class="token operator">:=</span> <span class="token boolean">false</span> <span class="token comment">// 此goroutine的饥饿标记</span>
     awoke <span class="token operator">:=</span> <span class="token boolean">false</span> <span class="token comment">// 唤醒标记</span>
     iter <span class="token operator">:=</span> <span class="token number">0</span> <span class="token comment">// 自旋次数</span>
     old <span class="token operator">:=</span> m<span class="token punctuation">.</span>state <span class="token comment">// 当前的锁的状态</span>
     <span class="token keyword">for</span> <span class="token punctuation">{</span>
         <span class="token comment">// 锁是非饥饿状态，锁还没被释放，尝试自旋</span>
         <span class="token keyword">if</span> old<span class="token operator">&amp;</span><span class="token punctuation">(</span>mutexLocked<span class="token operator">|</span>mutexStarving<span class="token punctuation">)</span> <span class="token operator">==</span> mutexLocked <span class="token operator">&amp;&amp;</span> <span class="token function">runtime_canSpin</span><span class="token punctuation">(</span>iter<span class="token punctuation">)</span> <span class="token punctuation">{</span>
             <span class="token keyword">if</span> <span class="token operator">!</span>awoke <span class="token operator">&amp;&amp;</span> old<span class="token operator">&amp;</span>mutexWoken <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> old<span class="token operator">&gt;&gt;</span>mutexWaiterShift <span class="token operator">!=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span>
                 atomic<span class="token punctuation">.</span><span class="token function">CompareAndSwapInt32</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>state<span class="token punctuation">,</span> old<span class="token punctuation">,</span> old<span class="token operator">|</span>mutexWoken<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                 awoke <span class="token operator">=</span> <span class="token boolean">true</span>
             <span class="token punctuation">}</span>
             <span class="token function">runtime_doSpin</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
             iter<span class="token operator">++</span>
             old <span class="token operator">=</span> m<span class="token punctuation">.</span>state <span class="token comment">// 再次获取锁的状态，之后会检查是否锁被释放了</span>
             <span class="token keyword">continue</span>
         <span class="token punctuation">}</span>
         <span class="token builtin">new</span> <span class="token operator">:=</span> old
         <span class="token keyword">if</span> old<span class="token operator">&amp;</span>mutexStarving <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span>
             <span class="token builtin">new</span> <span class="token operator">|=</span> mutexLocked <span class="token comment">// 非饥饿状态，加锁</span>
         <span class="token punctuation">}</span>
         <span class="token keyword">if</span> old<span class="token operator">&amp;</span><span class="token punctuation">(</span>mutexLocked<span class="token operator">|</span>mutexStarving<span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span>
             <span class="token builtin">new</span> <span class="token operator">+=</span> <span class="token number">1</span> <span class="token operator">&lt;&lt;</span> mutexWaiterShift <span class="token comment">// waiter数量加1</span>
         <span class="token punctuation">}</span>
         <span class="token keyword">if</span> starving <span class="token operator">&amp;&amp;</span> old<span class="token operator">&amp;</span>mutexLocked <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span>
             <span class="token builtin">new</span> <span class="token operator">|=</span> mutexStarving <span class="token comment">// 设置饥饿状态</span>
         <span class="token punctuation">}</span>
         <span class="token keyword">if</span> awoke <span class="token punctuation">{</span>
             <span class="token keyword">if</span> <span class="token builtin">new</span><span class="token operator">&amp;</span>mutexWoken <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span>
                 <span class="token function">throw</span><span class="token punctuation">(</span><span class="token string">"sync: inconsistent mutex state"</span><span class="token punctuation">)</span>
             <span class="token punctuation">}</span>
             <span class="token builtin">new</span> <span class="token operator">&amp;^=</span> mutexWoken <span class="token comment">// 新状态清除唤醒标记</span>
         <span class="token punctuation">}</span>
         <span class="token comment">// 成功设置新状态</span>
         <span class="token keyword">if</span> atomic<span class="token punctuation">.</span><span class="token function">CompareAndSwapInt32</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>state<span class="token punctuation">,</span> old<span class="token punctuation">,</span> <span class="token builtin">new</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
             <span class="token comment">// 原来锁的状态已释放，并且不是饥饿状态，正常请求到了锁，返回</span>
             <span class="token keyword">if</span> old<span class="token operator">&amp;</span><span class="token punctuation">(</span>mutexLocked<span class="token operator">|</span>mutexStarving<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span>
                 <span class="token keyword">break</span> <span class="token comment">// locked the mutex with CAS</span>
             <span class="token punctuation">}</span>
             <span class="token comment">// 处理饥饿状态</span>

             <span class="token comment">// 如果以前就在队列里面，加入到队列头</span>
             queueLifo <span class="token operator">:=</span> waitStartTime <span class="token operator">!=</span> <span class="token number">0</span>
             <span class="token keyword">if</span> waitStartTime <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span>
                 waitStartTime <span class="token operator">=</span> <span class="token function">runtime_nanotime</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
             <span class="token punctuation">}</span>
             <span class="token comment">// 阻塞等待</span>
             <span class="token function">runtime_SemacquireMutex</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>sema<span class="token punctuation">,</span> queueLifo<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span>
             <span class="token comment">// 唤醒之后检查锁是否应该处于饥饿状态</span>
             starving <span class="token operator">=</span> starving <span class="token operator">||</span> <span class="token function">runtime_nanotime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span>waitStartTime <span class="token operator">&gt;</span> starvationThresholdNs
             old <span class="token operator">=</span> m<span class="token punctuation">.</span>state
             <span class="token comment">// 如果锁已经处于饥饿状态，直接抢到锁，返回</span>
             <span class="token keyword">if</span> old<span class="token operator">&amp;</span>mutexStarving <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span>
                 <span class="token keyword">if</span> old<span class="token operator">&amp;</span><span class="token punctuation">(</span>mutexLocked<span class="token operator">|</span>mutexWoken<span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token number">0</span> <span class="token operator">||</span> old<span class="token operator">&gt;&gt;</span>mutexWaiterShift <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span>
                     <span class="token function">throw</span><span class="token punctuation">(</span><span class="token string">"sync: inconsistent mutex state"</span><span class="token punctuation">)</span>
                 <span class="token punctuation">}</span>
                 <span class="token comment">// 有点绕，加锁并且将waiter数减1</span>
                 delta <span class="token operator">:=</span> <span class="token function">int32</span><span class="token punctuation">(</span>mutexLocked <span class="token operator">-</span> <span class="token number">1</span><span class="token operator">&lt;&lt;</span>mutexWaiterShift<span class="token punctuation">)</span>
                 <span class="token keyword">if</span> <span class="token operator">!</span>starving <span class="token operator">||</span> old<span class="token operator">&gt;&gt;</span>mutexWaiterShift <span class="token operator">==</span> <span class="token number">1</span> <span class="token punctuation">{</span>
                     delta <span class="token operator">-=</span> mutexStarving <span class="token comment">// 最后一个waiter或者已经不饥饿了，清除饥饿标记</span>
                 <span class="token punctuation">}</span>
                 atomic<span class="token punctuation">.</span><span class="token function">AddInt32</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>state<span class="token punctuation">,</span> delta<span class="token punctuation">)</span>
                 <span class="token keyword">break</span>
             <span class="token punctuation">}</span>
             awoke <span class="token operator">=</span> <span class="token boolean">true</span>
             iter <span class="token operator">=</span> <span class="token number">0</span>
         <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
             old <span class="token operator">=</span> m<span class="token punctuation">.</span>state
         <span class="token punctuation">}</span>
     <span class="token punctuation">}</span>
 <span class="token punctuation">}</span>
 
 <span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>Mutex<span class="token punctuation">)</span> <span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
     <span class="token comment">// Fast path: drop lock bit.</span>
     <span class="token builtin">new</span> <span class="token operator">:=</span> atomic<span class="token punctuation">.</span><span class="token function">AddInt32</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>state<span class="token punctuation">,</span> <span class="token operator">-</span>mutexLocked<span class="token punctuation">)</span>
     <span class="token keyword">if</span> <span class="token builtin">new</span> <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span>
         m<span class="token punctuation">.</span><span class="token function">unlockSlow</span><span class="token punctuation">(</span><span class="token builtin">new</span><span class="token punctuation">)</span>
     <span class="token punctuation">}</span>
 <span class="token punctuation">}</span>
 
 <span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>Mutex<span class="token punctuation">)</span> <span class="token function">unlockSlow</span><span class="token punctuation">(</span><span class="token builtin">new</span> <span class="token builtin">int32</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
     <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token builtin">new</span><span class="token operator">+</span>mutexLocked<span class="token punctuation">)</span><span class="token operator">&amp;</span>mutexLocked <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span>
         <span class="token function">throw</span><span class="token punctuation">(</span><span class="token string">"sync: unlock of unlocked mutex"</span><span class="token punctuation">)</span>
     <span class="token punctuation">}</span>
     <span class="token keyword">if</span> <span class="token builtin">new</span><span class="token operator">&amp;</span>mutexStarving <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span>
         old <span class="token operator">:=</span> <span class="token builtin">new</span>
         <span class="token keyword">for</span> <span class="token punctuation">{</span>
             <span class="token keyword">if</span> old<span class="token operator">&gt;&gt;</span>mutexWaiterShift <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">||</span> old<span class="token operator">&amp;</span><span class="token punctuation">(</span>mutexLocked<span class="token operator">|</span>mutexWoken<span class="token operator">|</span>mutexStarving<span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span>
                 <span class="token keyword">return</span>
             <span class="token punctuation">}</span>
             <span class="token builtin">new</span> <span class="token operator">=</span> <span class="token punctuation">(</span>old <span class="token operator">-</span> <span class="token number">1</span><span class="token operator">&lt;&lt;</span>mutexWaiterShift<span class="token punctuation">)</span> <span class="token operator">|</span> mutexWoken
             <span class="token keyword">if</span> atomic<span class="token punctuation">.</span><span class="token function">CompareAndSwapInt32</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>state<span class="token punctuation">,</span> old<span class="token punctuation">,</span> <span class="token builtin">new</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                 <span class="token function">runtime_Semrelease</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>sema<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span>
                 <span class="token keyword">return</span>
             <span class="token punctuation">}</span>
             old <span class="token operator">=</span> m<span class="token punctuation">.</span>state
         <span class="token punctuation">}</span>
     <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
         <span class="token function">runtime_Semrelease</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>m<span class="token punctuation">.</span>sema<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span>
     <span class="token punctuation">}</span>
 <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>当超过饥饿模式的最大等待时间阈值，优先让等待goroutine获得锁。通过加入饥饿模式，可以避免把机会全都留给新来的 goroutine，保证了请求锁的 goroutine 获取锁的公平性，对于我们使用锁的业务代码来说，不会有业务一直等待锁不被处理。</p>
<h4 id="Mutex使用中常见bug">Mutex使用中常见bug<a class="anchor" href="#Mutex使用中常见bug">¶</a></h4>
<h5 id="Lock-Unlock-不是成对出现">Lock/Unlock 不是成对出现<a class="anchor" href="#Lock-Unlock-不是成对出现">¶</a></h5>
<p>Lock/Unlock 没有成对出现，就意味着会出现死锁的情况，或者是因为 Unlock 一个未加锁的 Mutex 而导致 panic。</p>
<p>常见情况：</p>
<ul>
<li>代码中有太多的 if-else 分支，可能在某个分支中漏写了 Unlock</li>
<li>在重构的时候把 Unlock 给删除了</li>
<li>Unlock 误写成了 Lock</li>
<li>Unlock 一个未加锁的 Mutex</li>
</ul>
<h5 id="Copy-已使用的-Mutex">Copy 已使用的 Mutex<a class="anchor" href="#Copy-已使用的-Mutex">¶</a></h5>
<p>Package sync 的同步原语在使用后是不能复制的。</p>
<p><strong>Mutex不能复制</strong>。因为Mutex是一个有状态的对象，其中的state字段记录了这个锁的状态。<strong>关键在并发的环境下，state状态是完全不确定的。</strong></p>
<p>可以使用 <strong>vet 工具</strong>在编译时检测是否存在复制Mutex现象，把检查写在 Makefile 文件中，在持续集成的时候跑一跑，这样可以及时发现问题，及时修复。</p>
<p>检查是通过<a target="_blank" rel="noopener" href="https://github.com/golang/tools/blob/master/go/analysis/passes/copylock/copylock.go">copylock</a>分析器静态分析实现的。这个分析器会分析函数调用、range 遍历、复制、声明、函数返回值等位置，有没有锁的值 copy 的情景，以此来判断有没有问题。可以说，<strong>只要是实现了 Locker 接口，就会被分析</strong>。我们看到，下面的代码就是确定什么类型会被分析，其实就是实现了 Lock/Unlock 两个方法的 Locker 接口：</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">var</span> lockerType <span class="token operator">*</span>types<span class="token punctuation">.</span>Interface
  
  <span class="token comment">// Construct a sync.Locker interface type.</span>
  <span class="token keyword">func</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    nullary <span class="token operator">:=</span> types<span class="token punctuation">.</span><span class="token function">NewSignature</span><span class="token punctuation">(</span><span class="token boolean">nil</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token comment">// func()</span>
    methods <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">*</span>types<span class="token punctuation">.</span>Func<span class="token punctuation">{</span>
      types<span class="token punctuation">.</span><span class="token function">NewFunc</span><span class="token punctuation">(</span>token<span class="token punctuation">.</span>NoPos<span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> <span class="token string">"Lock"</span><span class="token punctuation">,</span> nullary<span class="token punctuation">)</span><span class="token punctuation">,</span>
      types<span class="token punctuation">.</span><span class="token function">NewFunc</span><span class="token punctuation">(</span>token<span class="token punctuation">.</span>NoPos<span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> <span class="token string">"Unlock"</span><span class="token punctuation">,</span> nullary<span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span>
    lockerType <span class="token operator">=</span> types<span class="token punctuation">.</span><span class="token function">NewInterface</span><span class="token punctuation">(</span>methods<span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Complete</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>其实，有些没有实现 Locker 接口的同步原语（比如 WaitGroup），也能被分析。</p>
<p>Go 在运行时，有<strong>死锁的检查机制</strong>（<a target="_blank" rel="noopener" href="https://go.dev/src/runtime/proc.go?h=checkdead#L4345">checkdead()</a> 方法），它能够发现死锁的 goroutine。程序运行的时候，死锁检查机制能够发现这种死锁情况并输出错误信息，但是需要在运行时才能发现。</p>
<h5 id="重入">重入<a class="anchor" href="#重入">¶</a></h5>
<p>可重入锁：当一个线程获取锁时，如果没有其它线程拥有这个锁，那么，这个线程就成功获取到这个锁。之后，如果其它线程再请求这个锁，就会处于阻塞等待的状态。但是，如果拥有这把锁的线程再请求这把锁的话，不会阻塞，而是成功返回，所以叫可重入锁（有时候也叫做递归锁）。只要你拥有这把锁，你可以可着劲儿地调用，比如通过递归实现一些算法，调用者不会阻塞或者死锁。</p>
<p><strong>Mutex不是可重入锁。</strong></p>
<p>因为 Mutex 的实现中没有记录哪个 goroutine 拥有这把锁。理论上，任何 goroutine 都可以随意地 Unlock 这把锁，所以没办法计算重入条件。</p>
<p>实现一个可重入锁的关键在于：要记住当前是哪一个goroutine持有锁。</p>
<p>针对此有两种实现方式解决：</p>
<ul>
<li>通过 hacker 的方式获取到 goroutine id，记录下获取锁的 goroutine id，它可以实现 Locker 接口。</li>
<li>调用 Lock/Unlock 方法时，由 goroutine 提供一个 token，用来标识它自己，而不是我们通过 hacker 的方式获取到 goroutine id，但是，这样一来，就不满足 Locker 接口了。</li>
</ul>
<p>如果实现了标记持有锁的goroutine，那么相应也就能实现只有持有锁的goroutine才能unlock这个锁。</p>
<h6 id="goroutine-id">goroutine id<a class="anchor" href="#goroutine-id">¶</a></h6>
<p>这种方法关键在于获取goroutine id，大致可以分为两种：简单方式和hacker方式。</p>
<p>简单方式：通过 runtime.Stack 方法获取栈帧信息，栈帧信息。runtime.Stack 方法可以获取当前的goroutine，第二个参数为 true 会输出所有的 goroutine 信息。</p>
<h5 id="死锁">死锁<a class="anchor" href="#死锁">¶</a></h5>
<h3 id="饥饿模式和正常模式">饥饿模式和正常模式<a class="anchor" href="#饥饿模式和正常模式">¶</a></h3>
<pre class="mermaid">graph LR;
Mutex--&gt;饥饿模式
Mutex--&gt;正常模式</pre>
<p>首先分析一下 Mutex 对饥饿模式和正常模式的处理。</p>
<p>请求锁时调用的 Lock 方法中一开始是 fast path，这是一个幸运的场景，当前的 goroutine 幸运地获得了锁，没有竞争，直接返回，否则就进入了 lockSlow 方法。这样的设计，方便编译器对 Lock 方法进行内联，你也可以在程序开发中应用这个技巧。</p>
<p>正常模式下，waiter 都是进入先入先出队列，被唤醒的 waiter 并不会直接持有锁，而是要和新来的 goroutine 进行竞争。新来的 goroutine 有先天的优势，它们正在 CPU 中运行，可能它们的数量还不少，所以，在高并发情况下，被唤醒的 waiter 可能比较悲剧地获取不到锁，这时，它会被插入到队列的前面。如果 waiter 获取不到锁的时间超过阈值 1 毫秒，那么，这个 Mutex 就进入到了饥饿模式。</p>
<p>在饥饿模式下，Mutex 的拥有者将直接把锁交给队列最前面的 waiter。新来的 goroutine 不会尝试获取锁，即使看起来锁没有被持有，它也不会去抢，也不会 spin，它会乖乖地加入到等待队列的尾部。</p>
<p>如果拥有 Mutex 的 waiter 发现下面两种情况的其中之一，它就会把这个 Mutex 转换成正常模式:</p>
<ul>
<li>此 waiter 已经是队列中的最后一个 waiter 了，没有其它的等待锁的 goroutine 了；</li>
<li>此 waiter 的等待时间小于 1 毫秒。</li>
</ul>
<p>正常模式拥有更好的性能，因为即使有等待抢锁的 waiter，goroutine 也可以连续多次获取到锁。</p>
<p>饥饿模式是对公平性和性能的一种平衡，它避免了某些 goroutine 长时间的等待锁。在饥饿模式下，优先对待的是那些一直在等待的 waiter。</p>
<p>代码分析：</p>
<p>第 9 行对 state 字段又分出了一位，用来标记锁是否处于饥饿状态。现在一个 state 的字段被划分成了阻塞等待的 waiter 数量、饥饿标记、唤醒标记和持有锁的标记四个部分。</p>
<p>第 25 行记录此 goroutine 请求锁的初始时间，第 26 行标记是否处于饥饿状态，第 27 行标记是否是唤醒的，第 28 行记录 spin 的次数。</p>
<p>第 31 行到第 40 行和以前的逻辑类似，只不过加了一个不能是饥饿状态的逻辑。它会对正常状态抢夺锁的 goroutine 尝试 spin，和以前的目的一样，就是在临界区耗时很短的情况下提高性能。</p>
<p>第 42 行到第 44 行，非饥饿状态下抢锁。怎么抢？就是要把 state 的锁的那一位，置为加锁状态，后续 CAS 如果成功就可能获取到了锁。</p>
<p>第 46 行到第 48 行，如果锁已经被持有或者锁处于饥饿状态，我们最好的归宿就是等待，所以 waiter 的数量加 1。</p>
<p>第 49 行到第 51 行，如果此 goroutine 已经处在饥饿状态，并且锁还被持有，那么，我们需要把此 Mutex 设置为饥饿状态。</p>
<p>第 52 行到第 57 行，是清除 mutexWoken 标记，因为不管是获得了锁还是进入休眠，我们都需要清除 mutexWoken 标记。</p>
<p>第 59 行就是尝试使用 CAS 设置 state。如果成功，第 61 行到第 63 行是检查原来的锁的状态是未加锁状态，并且也不是饥饿状态的话就成功获取了锁，返回。</p>
<p>第 67 行判断是否第一次加入到 waiter 队列。到这里，你应该就能明白第 25 行为什么不对 waitStartTime 进行初始化了，我们需要利用它在这里进行条件判断。</p>
<p>第 72 行将此 waiter 加入到队列，如果是首次，加入到队尾，先进先出。如果不是首次，那么加入到队首，这样等待最久的 goroutine 优先能够获取到锁。此 goroutine 会进行休眠。</p>
<p>第 74 行判断此 goroutine 是否处于饥饿状态。注意，执行这一句的时候，它已经被唤醒了。</p>
<p>第 77 行到第 88 行是对锁处于饥饿状态下的一些处理。</p>
<p>第 82 行设置一个标志，这个标志稍后会用来加锁，而且还会将 waiter 数减 1。</p>
<p>第 84 行，设置标志，在没有其它的 waiter 或者此 goroutine 等待还没超过 1 毫秒，则会将 Mutex 转为正常状态。</p>
<p>第 86 行则是将这个标识应用到 state 字段上。</p>
<p>释放锁（Unlock）时调用的 Unlock 的 fast path 不用多少，所以我们主要看 unlockSlow 方法就行。</p>
<p>如果 Mutex 处于饥饿状态，第 123 行直接唤醒等待队列中的 waiter。</p>
<p>如果 Mutex 处于正常状态，如果没有 waiter，或者已经有在处理的情况了，那么释放就好，不做额外的处理（第 112 行到第 114 行）。</p>
<p>否则，waiter 数减 1，mutexWoken 标志设置上，通过 CAS 更新 state 的值（第 115 行到第 119 行）。</p>
<h2 id="原子操作">原子操作<a class="anchor" href="#原子操作">¶</a></h2>
<h2 id="Channel">Channel<a class="anchor" href="#Channel">¶</a></h2>
<h2 id="拓展并发原语">拓展并发原语<a class="anchor" href="#拓展并发原语">¶</a></h2>
<h2 id="分布式并发原语">分布式并发原语<a class="anchor" href="#分布式并发原语">¶</a></h2>
<h2 id="Tip">Tip<a class="anchor" href="#Tip">¶</a></h2>
<h3 id="race-detector">race detector<a class="anchor" href="#race-detector">¶</a></h3>
<p><a target="_blank" rel="noopener" href="https://go.dev/blog/race-detector">race detector</a>：一个检测并发访问资源是否有资源共享问题。<a target="_blank" rel="noopener" href="https://github.com/google/sanitizers">Go race detector</a> 是基于 Google 的 C/C++ sanitizers 技术实现的，编译器通过探测所有的内存访问，加入代码能监视对这些内存地址的访问（读还是写）。在代码运行的时候，race detector 就能监控到对共享变量的非同步访问，出现 race 的时候，就会打印出警告信息。</p>
<p>使用方式只需要在命令行参数后面加上<code>-race</code>即可。</p>
<p>缺点：</p>
<ul>
<li>因为它的实现方式，<strong>只能通过真正对实际地址进行读写访问的时候才能探测</strong>，所以它并不能在编译的时候发现 data race 的问题。而且，在运行的时候，只有在触发了 data race 之后，才能检测到，如果碰巧没有触发是检测不出来的。</li>
<li>最好别部署到线上，影响性能。</li>
</ul>
<h3 id="goroutine等待机制">goroutine等待机制<a class="anchor" href="#goroutine等待机制">¶</a></h3>
<p>等待的goroutine们是以FIFO排队的</p>
<ul>
<li>
<p>当Mutex处于正常模式时，若此时没有新goroutine与队头goroutine竞争，则队头goroutine获得。若有新goroutine竞争大概率新goroutine获得。</p>
</li>
<li>
<p>当队头goroutine竞争锁失败1ms后，它会将Mutex调整为饥饿模式。进入饥饿模式后，锁的所有权会直接从解锁goroutine移交给队头goroutine，此时新来的goroutine直接放入队尾。</p>
</li>
<li>
<p>当一个goroutine获取锁后，如果发现自己满足下列条件中的任何一个</p>
<ol>
<li>
<p>它是队列中最后一个</p>
</li>
<li>
<p>它等待锁的时间少于1ms，</p>
<p>则将锁切换回正常模式</p>
</li>
</ol>
</li>
</ul>
<p>参考<a target="_blank" rel="noopener" href="https://go.dev/src/sync/mutex.go">mutex代码实现中的注释</a>。</p>
<h2 id="参考文献">参考文献<a class="anchor" href="#参考文献">¶</a></h2>
<ol>
<li>
<p>极客时间，<a target="_blank" rel="noopener" href="https://time.geekbang.org/column/intro/100061801">Go并发变成实战课</a></p>
</li>
<li>
<p><a target="_blank" rel="noopener" href="https://go.dev/src/">Go源码</a></p>
</li>
<li>
<p>[^3 ]:<a target="_blank" rel="noopener" href="https://studygolang.com/articles/16480">golang 自旋锁</a></p>
</li>
</ol>

                
            </div>
            <hr/>

            

    <div class="reprint" id="reprint-statement">
        
            <div class="reprint__author">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-user">
                        文章作者:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="/about" rel="external nofollow noreferrer">不二</a>
                </span>
            </div>
            <div class="reprint__type">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-link">
                        文章链接:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="http://buerlog.top/2022/03/12/cheng-xu-she-ji/go/go-bing-fa-bian-cheng-zheng-li/">http://buerlog.top/2022/03/12/cheng-xu-she-ji/go/go-bing-fa-bian-cheng-zheng-li/</a>
                </span>
            </div>
            <div class="reprint__notice">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-copyright">
                        版权声明:
                    </i>
                </span>
                <span class="reprint-info">
                    本博客所有文章除特別声明外，均采用
                    <a href="https://creativecommons.org/licenses/by/4.0/deed.zh" rel="external nofollow noreferrer" target="_blank">CC BY 4.0</a>
                    许可协议。转载请注明来源
                    <a href="/about" target="_blank">不二</a>
                    !
                </span>
            </div>
        
    </div>

    <script async defer>
      document.addEventListener("copy", function (e) {
        let toastHTML = '<span>复制成功，请遵循本文的转载规则</span><button class="btn-flat toast-action" onclick="navToReprintStatement()" style="font-size: smaller">查看</a>';
        M.toast({html: toastHTML})
      });

      function navToReprintStatement() {
        $("html, body").animate({scrollTop: $("#reprint-statement").offset().top - 80}, 800);
      }
    </script>



            <div class="tag_share" style="display: block;">
                <div class="post-meta__tag-list" style="display: inline-block;">
                    
                        <div class="article-tag">
                            
                                <a href="/tags/Go/">
                                    <span class="chip bg-color">Go</span>
                                </a>
                            
                        </div>
                    
                </div>
                <div class="post_share" style="zoom: 80%; width: fit-content; display: inline-block; float: right; margin: -0.15rem 0;">
                    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/share/css/share.min.css">
<div id="article-share">

    
    <div class="social-share" data-sites="twitter,facebook,google,qq,qzone,wechat,weibo,douban,linkedin" data-wechat-qrcode-helper="<p>微信扫一扫即可分享！</p>"></div>
    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/share/js/social-share.min.js"></script>
    

    

</div>

                </div>
            </div>
            
                <style>
    #reward {
        margin: 40px 0;
        text-align: center;
    }

    #reward .reward-link {
        font-size: 1.4rem;
        line-height: 38px;
    }

    #reward .btn-floating:hover {
        box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2), 0 5px 15px rgba(0, 0, 0, 0.2);
    }

    #rewardModal {
        width: 320px;
        height: 350px;
    }

    #rewardModal .reward-title {
        margin: 15px auto;
        padding-bottom: 5px;
    }

    #rewardModal .modal-content {
        padding: 10px;
    }

    #rewardModal .close {
        position: absolute;
        right: 15px;
        top: 15px;
        color: rgba(0, 0, 0, 0.5);
        font-size: 1.3rem;
        line-height: 20px;
        cursor: pointer;
    }

    #rewardModal .close:hover {
        color: #ef5350;
        transform: scale(1.3);
        -moz-transform:scale(1.3);
        -webkit-transform:scale(1.3);
        -o-transform:scale(1.3);
    }

    #rewardModal .reward-tabs {
        margin: 0 auto;
        width: 210px;
    }

    .reward-tabs .tabs {
        height: 38px;
        margin: 10px auto;
        padding-left: 0;
    }

    .reward-content ul {
        padding-left: 0 !important;
    }

    .reward-tabs .tabs .tab {
        height: 38px;
        line-height: 38px;
    }

    .reward-tabs .tab a {
        color: #fff;
        background-color: #ccc;
    }

    .reward-tabs .tab a:hover {
        background-color: #ccc;
        color: #fff;
    }

    .reward-tabs .wechat-tab .active {
        color: #fff !important;
        background-color: #22AB38 !important;
    }

    .reward-tabs .alipay-tab .active {
        color: #fff !important;
        background-color: #019FE8 !important;
    }

    .reward-tabs .reward-img {
        width: 210px;
        height: 210px;
    }
</style>

<div id="reward">
    <a href="#rewardModal" class="reward-link modal-trigger btn-floating btn-medium waves-effect waves-light red">赏</a>

    <!-- Modal Structure -->
    <div id="rewardModal" class="modal">
        <div class="modal-content">
            <a class="close modal-close"><i class="fas fa-times"></i></a>
            <h4 class="reward-title">你的赏识是我前进的动力</h4>
            <div class="reward-content">
                <div class="reward-tabs">
                    <ul class="tabs row">
                        <li class="tab col s6 alipay-tab waves-effect waves-light"><a href="#alipay">支付宝</a></li>
                        <li class="tab col s6 wechat-tab waves-effect waves-light"><a href="#wechat">微 信</a></li>
                    </ul>
                    <div id="alipay">
                        <img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/medias/reward/alipay.jpg" class="reward-img" alt="支付宝打赏二维码">
                    </div>
                    <div id="wechat">
                        <img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/medias/reward/wechat.png" class="reward-img" alt="微信打赏二维码">
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    $(function () {
        $('.tabs').tabs();
    });
</script>

            
        </div>
    </div>

    

    

    

    

    

    

    

    

    

<article id="prenext-posts" class="prev-next articles">
    <div class="row article-row">
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge left-badge text-color">
                <i class="fas fa-chevron-left"></i>&nbsp;上一篇</div>
            <div class="card">
                <a href="/2022/03/14/jing-yan-zong-jie/ka-pei-xue-xi-bi-ji/">
                    <div class="card-image">
                        
                        <img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/PageImg/经验总结/咖啡学习笔记.jpeg" class="responsive-img" alt="咖啡学习笔记">
                        
                        <span class="card-title">咖啡学习笔记</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            
                        
                    </div>
                    <div class="publish-info">
                        <span class="publish-date">
                            <i class="far fa-clock fa-fw icon-date"></i>2022-03-14
                        </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/categories/%E7%BB%8F%E9%AA%8C%E6%80%BB%E7%BB%93/" class="post-category">
                                    经验总结
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/tags/%E5%92%96%E5%95%A1/">
                        <span class="chip bg-color">咖啡</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge right-badge text-color">
                下一篇&nbsp;<i class="fas fa-chevron-right"></i>
            </div>
            <div class="card">
                <a href="/2022/03/12/cheng-xu-she-ji/xiao-xi-dui-lie-xue-xi/">
                    <div class="card-image">
                        
                        <img src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@src/source/_posts/PageImg/程序设计/消息队列学习.png" class="responsive-img" alt="消息队列学习">
                        
                        <span class="card-title">消息队列学习</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            
                        
                    </div>
                    <div class="publish-info">
                            <span class="publish-date">
                                <i class="far fa-clock fa-fw icon-date"></i>2022-03-12
                            </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/categories/%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/" class="post-category">
                                    程序设计
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/tags/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/">
                        <span class="chip bg-color">消息队列</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
    </div>
</article>

</div>


<script>
    $('#articleContent').on('copy', function (e) {
        // IE8 or earlier browser is 'undefined'
        if (typeof window.getSelection === 'undefined') return;

        var selection = window.getSelection();
        // if the selection is short let's not annoy our users.
        if (('' + selection).length < Number.parseInt('120')) {
            return;
        }

        // create a div outside of the visible area and fill it with the selected text.
        var bodyElement = document.getElementsByTagName('body')[0];
        var newdiv = document.createElement('div');
        newdiv.style.position = 'absolute';
        newdiv.style.left = '-99999px';
        bodyElement.appendChild(newdiv);
        newdiv.appendChild(selection.getRangeAt(0).cloneContents());

        // we need a <pre> tag workaround.
        // otherwise the text inside "pre" loses all the line breaks!
        if (selection.getRangeAt(0).commonAncestorContainer.nodeName === 'PRE') {
            newdiv.innerHTML = "<pre>" + newdiv.innerHTML + "</pre>";
        }

        var url = document.location.href;
        newdiv.innerHTML += '<br />'
            + '来源: 不二博客<br />'
            + '文章作者: 不二<br />'
            + '文章链接: <a href="' + url + '">' + url + '</a><br />'
            + '本文章著作权归作者所有，任何形式的转载都请注明出处。';

        selection.selectAllChildren(newdiv);
        window.setTimeout(function () {bodyElement.removeChild(newdiv);}, 200);
    });
</script>


<!-- 代码块功能依赖 -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/codeBlock/codeBlockFuction.js"></script>

<!-- 代码语言 -->

<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/codeBlock/codeLang.js"></script>


<!-- 代码块复制 -->

<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/codeBlock/codeCopy.js"></script>


<!-- 代码块收缩 -->

<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/codeBlock/codeShrink.js"></script>


    </div>
    <div id="toc-aside" class="expanded col l3 hide-on-med-and-down">
        <div class="toc-widget card" style="background-color: white;">
            <div class="toc-title"><i class="far fa-list-alt"></i>&nbsp;&nbsp;目录</div>
            <div id="toc-content"></div>
        </div>
    </div>
</div>

<!-- TOC 悬浮按钮. -->

<div id="floating-toc-btn" class="hide-on-med-and-down">
    <a class="btn-floating btn-large bg-color">
        <i class="fas fa-list-ul"></i>
    </a>
</div>


<script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/tocbot/tocbot.min.js"></script>
<script>
    $(function () {
        tocbot.init({
            tocSelector: '#toc-content',
            contentSelector: '#articleContent',
            headingsOffset: -($(window).height() * 0.4 - 45),
            collapseDepth: Number('0'),
            headingSelector: 'h2, h3, h4, h5, h6'
        });

        // modify the toc link href to support Chinese.
        let i = 0;
        let tocHeading = 'toc-heading-';
        $('#toc-content a').each(function () {
            $(this).attr('href', '#' + tocHeading + (++i));
        });

        // modify the heading title id to support Chinese.
        i = 0;
        $('#articleContent').children('h2, h3, h4, h5, h6').each(function () {
            $(this).attr('id', tocHeading + (++i));
        });

        // Set scroll toc fixed.
        let tocHeight = parseInt($(window).height() * 0.4 - 64);
        let $tocWidget = $('.toc-widget');
        $(window).scroll(function () {
            let scroll = $(window).scrollTop();
            /* add post toc fixed. */
            if (scroll > tocHeight) {
                $tocWidget.addClass('toc-fixed');
            } else {
                $tocWidget.removeClass('toc-fixed');
            }
        });

        
        /* 修复文章卡片 div 的宽度. */
        let fixPostCardWidth = function (srcId, targetId) {
            let srcDiv = $('#' + srcId);
            if (srcDiv.length === 0) {
                return;
            }

            let w = srcDiv.width();
            if (w >= 450) {
                w = w + 21;
            } else if (w >= 350 && w < 450) {
                w = w + 18;
            } else if (w >= 300 && w < 350) {
                w = w + 16;
            } else {
                w = w + 14;
            }
            $('#' + targetId).width(w);
        };

        // 切换TOC目录展开收缩的相关操作.
        const expandedClass = 'expanded';
        let $tocAside = $('#toc-aside');
        let $mainContent = $('#main-content');
        $('#floating-toc-btn .btn-floating').click(function () {
            if ($tocAside.hasClass(expandedClass)) {
                $tocAside.removeClass(expandedClass).hide();
                $mainContent.removeClass('l9');
            } else {
                $tocAside.addClass(expandedClass).show();
                $mainContent.addClass('l9');
            }
            fixPostCardWidth('artDetail', 'prenext-posts');
        });
        
    });
</script>

    

</main>




    <footer class="page-footer bg-color">
    
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/aplayer/APlayer.min.css">
<style>
    .aplayer .aplayer-lrc p {
        
        display: none;
        
        font-size: 12px;
        font-weight: 700;
        line-height: 16px !important;
    }

    .aplayer .aplayer-lrc p.aplayer-lrc-current {
        
        display: none;
        
        font-size: 15px;
        color: #42b983;
    }

    
    .aplayer.aplayer-fixed.aplayer-narrow .aplayer-body {
        left: -66px !important;
    }

    .aplayer.aplayer-fixed.aplayer-narrow .aplayer-body:hover {
        left: 0px !important;
    }

    
</style>
<div class="">
    
    <div class="row">
        <meting-js class="col l8 offset-l2 m10 offset-m1 s12"
                   server="netease"
                   type="song"
                   id="1901371647"
                   fixed='true'
                   autoplay='false'
                   theme='#42b983'
                   loop='all'
                   order='random'
                   preload='auto'
                   volume='0.7'
                   list-folded='true'
        >
        </meting-js>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/aplayer/APlayer.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js"></script>

    
    <div class="container row center-align" style="margin-bottom: 15px !important;">
        <div class="col s12 m8 l8 copy-right">
            Copyright&nbsp;&copy;
            
                <span id="year">2018-2023</span>
            
            <span id="year">2018</span>
            <a href="/about" target="_blank">不二</a>
            |&nbsp;Powered by&nbsp;<a href="https://hexo.io/" target="_blank">Hexo</a>
            |&nbsp;Theme&nbsp;<a href="https://github.com/blinkfox/hexo-theme-matery" target="_blank">Matery</a>
            <br>
            
            &nbsp;<i class="fas fa-chart-area"></i>&nbsp;站点总字数:&nbsp;<span
                class="white-color">263.4k</span>&nbsp;字
            
            
            
            
            
            
            <span id="busuanzi_container_site_pv">
                |&nbsp;<i class="far fa-eye"></i>&nbsp;总访问量:&nbsp;<span id="busuanzi_value_site_pv"
                    class="white-color"></span>&nbsp;次
            </span>
            
            
            <span id="busuanzi_container_site_uv">
                |&nbsp;<i class="fas fa-users"></i>&nbsp;总访问人数:&nbsp;<span id="busuanzi_value_site_uv"
                    class="white-color"></span>&nbsp;人
            </span>
            
            <br>
            
            <span id="sitetime">载入运行时间...</span>
            <script>
                function siteTime() {
                    var seconds = 1000;
                    var minutes = seconds * 60;
                    var hours = minutes * 60;
                    var days = hours * 24;
                    var years = days * 365;
                    var today = new Date();
                    var startYear = "2018";
                    var startMonth = "5";
                    var startDate = "25";
                    var startHour = "17";
                    var startMinute = "20";
                    var startSecond = "53";
                    var todayYear = today.getFullYear();
                    var todayMonth = today.getMonth() + 1;
                    var todayDate = today.getDate();
                    var todayHour = today.getHours();
                    var todayMinute = today.getMinutes();
                    var todaySecond = today.getSeconds();
                    var t1 = Date.UTC(startYear, startMonth, startDate, startHour, startMinute, startSecond);
                    var t2 = Date.UTC(todayYear, todayMonth, todayDate, todayHour, todayMinute, todaySecond);
                    var diff = t2 - t1;
                    var diffYears = Math.floor(diff / years);
                    var diffDays = Math.floor((diff / days) - diffYears * 365);
                    var diffHours = Math.floor((diff - (diffYears * 365 + diffDays) * days) / hours);
                    var diffMinutes = Math.floor((diff - (diffYears * 365 + diffDays) * days - diffHours * hours) /
                        minutes);
                    var diffSeconds = Math.floor((diff - (diffYears * 365 + diffDays) * days - diffHours * hours -
                        diffMinutes * minutes) / seconds);
                    if (startYear == todayYear) {
                        document.getElementById("year").innerHTML = todayYear;
                        document.getElementById("sitetime").innerHTML = "本站已安全运行 " + diffDays + " 天 " + diffHours +
                            " 小时 " + diffMinutes + " 分钟 " + diffSeconds + " 秒";
                    } else {
                        document.getElementById("year").innerHTML = startYear + " - " + todayYear;
                        document.getElementById("sitetime").innerHTML = "本站已安全运行 " + diffYears + " 年 " + diffDays +
                            " 天 " + diffHours + " 小时 " + diffMinutes + " 分钟 " + diffSeconds + " 秒";
                    }
                }
                setInterval(siteTime, 1000);
            </script>
            
            <br>
            
        </div>
        <div class="col s12 m4 l4 social-link social-statis">
    <a href="https://github.com/weiyouwozuiku" class="tooltipped" target="_blank" data-tooltip="访问我的GitHub" data-position="top" data-delay="50">
        <i class="fab fa-github"></i>
    </a>



    <a href="mailto:weiyouwozuiku@gmail.com" class="tooltipped" target="_blank" data-tooltip="邮件联系我" data-position="top" data-delay="50">
        <i class="fas fa-envelope-open"></i>
    </a>







    <a href="tencent://AddContact/?fromId=50&fromSubId=1&subcmd=all&uin=1104891151" class="tooltipped" target="_blank" data-tooltip="QQ联系我: 1104891151" data-position="top" data-delay="50">
        <i class="fab fa-qq"></i>
    </a>





    <a href="https://www.zhihu.com/people/he-he-9-35-47" class="tooltipped" target="_blank" data-tooltip="关注我的知乎: https://www.zhihu.com/people/he-he-9-35-47" data-position="top" data-delay="50">
        <i class="fab fa-zhihu1">知</i>
    </a>



    <a href="/atom.xml" class="tooltipped" target="_blank" data-tooltip="RSS 订阅" data-position="top" data-delay="50">
        <i class="fas fa-rss"></i>
    </a>

</div>
    </div>
</footer>

<div class="progress-bar"></div>

    <script src='https://unpkg.com/mermaid@8.14.0/dist/mermaid.min.js'></script>
    <script>
      if (window.mermaid) {
        mermaid.initialize({theme: 'forest'});
      }
    </script>
  

    <!-- 搜索遮罩框 -->
<div id="searchModal" class="modal">
    <div class="modal-content">
        <div class="search-header">
            <span class="title"><i class="fas fa-search"></i>&nbsp;&nbsp;搜索</span>
            <input type="search" id="searchInput" name="s" placeholder="请输入搜索的关键字"
                   class="search-input">
        </div>
        <div id="searchResult"></div>
    </div>
</div>

<script type="text/javascript">
$(function () {
    var searchFunc = function (path, search_id, content_id) {
        'use strict';
        $.ajax({
            url: path,
            dataType: "xml",
            success: function (xmlResponse) {
                // get the contents from search data
                var datas = $("entry", xmlResponse).map(function () {
                    return {
                        title: $("title", this).text(),
                        content: $("content", this).text(),
                        url: $("url", this).text()
                    };
                }).get();
                var $input = document.getElementById(search_id);
                var $resultContent = document.getElementById(content_id);
                $input.addEventListener('input', function () {
                    var str = '<ul class=\"search-result-list\">';
                    var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
                    $resultContent.innerHTML = "";
                    if (this.value.trim().length <= 0) {
                        return;
                    }
                    // perform local searching
                    datas.forEach(function (data) {
                        var isMatch = true;
                        var data_title = data.title.trim().toLowerCase();
                        var data_content = data.content.trim().replace(/<[^>]+>/g, "").toLowerCase();
                        var data_url = data.url;
                        data_url = data_url.indexOf('/') === 0 ? data.url : '/' + data_url;
                        var index_title = -1;
                        var index_content = -1;
                        var first_occur = -1;
                        // only match artiles with not empty titles and contents
                        if (data_title !== '' && data_content !== '') {
                            keywords.forEach(function (keyword, i) {
                                index_title = data_title.indexOf(keyword);
                                index_content = data_content.indexOf(keyword);
                                if (index_title < 0 && index_content < 0) {
                                    isMatch = false;
                                } else {
                                    if (index_content < 0) {
                                        index_content = 0;
                                    }
                                    if (i === 0) {
                                        first_occur = index_content;
                                    }
                                }
                            });
                        }
                        // show search results
                        if (isMatch) {
                            str += "<li><a href='" + data_url + "' class='search-result-title'>" + data_title + "</a>";
                            var content = data.content.trim().replace(/<[^>]+>/g, "");
                            if (first_occur >= 0) {
                                // cut out 100 characters
                                var start = first_occur - 20;
                                var end = first_occur + 80;
                                if (start < 0) {
                                    start = 0;
                                }
                                if (start === 0) {
                                    end = 100;
                                }
                                if (end > content.length) {
                                    end = content.length;
                                }
                                var match_content = content.substr(start, end);
                                // highlight all keywords
                                keywords.forEach(function (keyword) {
                                    var regS = new RegExp(keyword, "gi");
                                    match_content = match_content.replace(regS, "<em class=\"search-keyword\">" + keyword + "</em>");
                                });

                                str += "<p class=\"search-result\">" + match_content + "...</p>"
                            }
                            str += "</li>";
                        }
                    });
                    str += "</ul>";
                    $resultContent.innerHTML = str;
                });
            }
        });
    };

    searchFunc('/search.xml', 'searchInput', 'searchResult');
});
</script>

    <!-- 回到顶部按钮 -->
<div id="backTop" class="top-scroll">
    <a class="btn-floating btn-large waves-effect waves-light" href="#!">
        <i class="fas fa-arrow-up"></i>
    </a>
</div>


    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/materialize/materialize.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/masonry/masonry.pkgd.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/aos/aos.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/scrollprogress/scrollProgress.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/lightGallery/js/lightgallery-all.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/js/matery.js"></script>

    <!-- Baidu Analytics -->

<script>
    var _hmt = _hmt || [];
    (function () {
        var hm = document.createElement("script");
        hm.src = "https://hm.baidu.com/hm.js?943a68f473d421f2d98228f74edc63d0";
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(hm, s);
    })();
</script>

    <!-- Baidu Push -->

<script>
    (function () {
        var bp = document.createElement('script');
        var curProtocol = window.location.protocol.split(':')[0];
        if (curProtocol === 'https') {
            bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
        } else {
            bp.src = 'http://push.zhanzhang.baidu.com/push.js';
        }
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(bp, s);
    })();
</script>

    
    
    <script async src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/others/busuanzi.pure.mini.js"></script>
    

    

    

    <!--腾讯兔小巢-->
    
    
    <script type="text/javascript" color="0,0,255"
        pointColor="0,0,255" opacity='0.7'
        zIndex="-1" count="150"
        src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/background/canvas-nest.js"></script>
    

    
    
    <script type="text/javascript" size="150" alpha='0.6'
        zIndex="-1" src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/background/ribbon-refresh.min.js" async="async"></script>
    

    
    <script type="text/javascript" src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/background/ribbon-dynamic.js" async="async"></script>
    

    
    <script src="https://cdn.jsdelivr.net/gh/weiyouwozuiku/weiyouwozuiku.github.io@master/libs/instantpage/instantpage.js" type="module"></script>
    

</body>

</html>
